//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//

#include <windows.h>
#include <ceddk.h>

#include "Cs.h"
#include "Csmedia.h"

#include "CameraPDDProps.h"
#include "dstruct.h"
#include "dbgsettings.h"
#include <camera.h>
#include "CameraDriver.h"
#include "PinDriver.h"
#include "SensorFormats.h"

#include "monahans.h"
#include "Ov2630.h"
#include "xllp_Ov2630_hw.h"
#include "xllp_camera.h"
#include "xllp_ost.h"
#include "xllp_i2c.h"
#include "xllp_gpio_plat.h"
#include "GPX_XLLP.h"

extern "C" unsigned int gpio_reg;
extern "C" unsigned int ost_reg;
extern "C" unsigned int i2c_reg;

MAKE_STREAM_MODE_YV12(DCAM_StreamMode_CIF_YV12, 352, -288, 12, 15);
MAKE_STREAM_MODE_YV12(DCAM_StreamMode_SVGA_YV12, 800, -600, 12, 30);
MAKE_STREAM_MODE_YV12(DCAM_StreamMode_UXGA_YV12, 1600, -1200, 12, 15);
UCHAR get_analog_gain(USHORT gain);
void get_digi_gain(USHORT gain, UCHAR* digi_gain1, UCHAR* digi_gain2);

static color_cfg_t color_cfg = 
{
    0,
    0,
    { 
        1.036, 0.097, -0.133,
        -0.006, 1.217, -0.211,
        0.012, -0.386, 1.374
    }
};

static camera_cfg_t preview_cfg = 
{
    &DCAM_StreamMode_CIF_YV12,
    XLLP_CAMERA_IMAGE_FORMAT_RAW10,     /* sensor format */
    CIF,
    /* qci_format_t */
    {
        XLLP_CAMERA_IMAGE_FORMAT_RAW10,
        352, 288
    },
    &color_cfg
};

static camera_cfg_t still_svga_cfg = 
{
    &DCAM_StreamMode_SVGA_YV12,
    XLLP_CAMERA_IMAGE_FORMAT_RAW10,     /* sensor format */
    SVGA,
    /* qci_format_t */
    {
        XLLP_CAMERA_IMAGE_FORMAT_YCBCR422_PLANAR,
        800, 600
    },
    &color_cfg
};

static camera_cfg_t still_cfg = 
{
    &DCAM_StreamMode_UXGA_YV12,
    XLLP_CAMERA_IMAGE_FORMAT_RAW10,     /* sensor format */
    UXGA,
	/* qci_format_t */
    {
        XLLP_CAMERA_IMAGE_FORMAT_RAW10,
        1600, 1200
    },
	&color_cfg
};

static qci_interface_t ov2630_interface =
{
    XLLP_CI_MODE_MP,
    XLLP_CI_DATA_WIDTH10,
    2600,
    XLLP_FALSE, 
    XLLP_FALSE, 
    XLLP_FALSE
};

static unsigned char lut[] = {
    // RED LUT
    0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c,
    0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c,
    0x40, 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c,
    0x60, 0x64, 0x68, 0x6c, 0x70, 0x74, 0x78, 0x7c,
    0x80, 0x84, 0x88, 0x8c, 0x90, 0x94, 0x98, 0x9c,
    0xa0, 0xa4, 0xa8, 0xac, 0xb0, 0xb4, 0xb8, 0xbc,
    0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
    0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf4, 0xf8, 0xfc,

    // BLUE LUT
    0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c,
    0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c,
    0x40, 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c,
    0x60, 0x64, 0x68, 0x6c, 0x70, 0x74, 0x78, 0x7c,
    0x80, 0x84, 0x88, 0x8c, 0x90, 0x94, 0x98, 0x9c,
    0xa0, 0xa4, 0xa8, 0xac, 0xb0, 0xb4, 0xb8, 0xbc,
    0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
    0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf4, 0xf8, 0xfc,

    // GREEN LUT
    0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c,
    0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c,
    0x40, 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c,
    0x60, 0x64, 0x68, 0x6c, 0x70, 0x74, 0x78, 0x7c,
    0x80, 0x84, 0x88, 0x8c, 0x90, 0x94, 0x98, 0x9c,
    0xa0, 0xa4, 0xa8, 0xac, 0xb0, 0xb4, 0xb8, 0xbc,
    0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
    0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf4, 0xf8, 0xfc,
};



#define SENSOR_INIT_WAIT  150
void map_registers();
static void dump_Ov2630();

static IppiRawPixProcSpec_P3R* rggb2yuv_init(unsigned int width,
                                      unsigned int height,
                                      float* coe);
static BOOL rggb2yuv_convert(PUCHAR* dest, frame_t* frame, IppiRawPixProcSpec_P3R *ippiRPPSpec);
static void rggb2yuv_deinit(IppiRawPixProcSpec_P3R *ippiRPPSpec);
extern void fill_buffer_yv12(PUCHAR* dest_plane, frame_t* frame);
Ov2630::Ov2630()
{
    map_registers();
    qci_interface = &ov2630_interface;
    timing.BFW = 0x01;
    timing.BLW = 0x01;   /*GRBG to RGGB*/  

    rpp_uxga = rggb2yuv_init(1600, 1200, color_cfg.color_correct_coe);
    rpp_cif = rggb2yuv_init(352, 288, color_cfg.color_correct_coe);
}

bool Ov2630::detect()
{
    XLLP_UINT8_T pid = 0, rev = 0;

    OV2630VersionRevision(&pid, &rev);
    NKDbgPrintfW(L"2630 sensor pid %d, rev %d", pid, rev);

    return pid == PIDH_OV2630 && rev == PIDL_OV2630;
}

unsigned int Ov2630::get_formats(ULONG type, PCS_DATARANGE_VIDEO** formats)
{
    // Video Format initialization
    unsigned int nformats = 1;

    *formats = new PCS_DATARANGE_VIDEO[nformats];

    if( NULL == *formats )
    {
        return 0;
    }

    switch (type)
    {
    case CAPTURE:
    case PREVIEW:
        (*formats)[0] = &DCAM_StreamMode_CIF_YV12;
        break;
    case STILL:
        (*formats)[0] = &DCAM_StreamMode_UXGA_YV12;
        break;
    default:
        break;
    }
    return nformats;
}

void Ov2630::start_capture()
{
    USHORT exposure = 250;
    USHORT gain = 4;

    if (frame_size == UXGA)
    {
        exposure *= 2;
    }

    OV2630AutoFunctionOff();
    OV2630ViewfinderOn();
    set_exposure_gain(exposure, gain);
    // dump_Ov2630();
}

void Ov2630::stop_capture()
{
    OV2630ViewfinderOff();
}

extern void set_voltage(UCHAR camera_ana, UCHAR camera_io, UCHAR camera_core, BOOL is_on);

void Ov2630::set_power_mode(bool is_on)
{
    set_voltage(0xa, 0xa, 0x0, is_on);
#if 0
    /* Call_GPX */
    GPX_SetDirection(XLLP_GPIO_CAMERA_HI_PWDN, XLLP_GPIO_DIRECTION_OUT);
#endif

    OV2630PowerDown(gpio_reg, ost_reg, 
                    is_on? XLLP_CAMERA_POWER_FULL : XLLP_CAMERA_POWER_OFF);
    if (is_on)
        Sleep(200);
}

camera_cfg_t* Ov2630::get_camera_cfg(PCS_VIDEOINFOHEADER pCsVideoInfoHdr, ULONG mode)
{
    UINT width = abs(pCsVideoInfoHdr->bmiHeader.biWidth);
    UINT height = abs(pCsVideoInfoHdr->bmiHeader.biHeight);

    if (width == 352 && height == 288)
        return &preview_cfg;
    else if (width == 1600 && height == 1200)
	return &still_cfg;
    else if (width == 800 && height == 600)
        return &still_svga_cfg;
	return 0;
}

void Ov2630::set_frame_format(int format, FrameSize size)
{
    OV2630Reset();
    frame_size = size;
    //XLLP_CI_MP_TIMING timing;
    //int status;
    //XLLP_UINT32_T ovSizeFormat, ovFormat;
    XLLP_UINT32_T width, height;
    XLLP_UINT32_T x[2], y[2];

    switch(size)
    {
    case CIF:
        width = 352;
        height = 288;
        break;
    case UXGA:
    default:
        width = 1600;
        height = 1200;
        break;
    }

    OV2630SetFormat(width, height, &x[0], &y[0], &x[1], &y[1]);
}

void Ov2630::handle_frame_interrupt(PUCHAR buf,
                                    CS_BITMAPINFOHEADER* info_header,
                                    frame_t* frame)
{
    UINT width = abs(info_header->biWidth);
    UINT height = abs(info_header->biHeight);
    UINT y_size = width * height;
    format_t* format = &frame->list->format;

    if (width != format->width && height != format->height)
    {
        NKDbgPrintfW(L"unexpected frame size!!!");
        return;
    }

    PUCHAR dest[3];

    dest[0] = buf;
    dest[2] = dest[0] + y_size;
    dest[1] = dest[2]  + (y_size >> 2);

    switch (format->format)
    {
    case XLLP_CAMERA_IMAGE_FORMAT_YCBCR422_PLANAR:
        fill_buffer_yv12(dest, frame);
        break;
    case XLLP_CAMERA_IMAGE_FORMAT_RAW10:
        NKDbgPrintfW(L"converting raw data!!!");
        handle_frame_raw10(buf, frame);
        break;
    default:
        NKDbgPrintfW(L"unsupported frame format %d!!!", format->format);
    }
    return;
}

void Ov2630::handle_frame_raw10(PUCHAR buf, frame_t* frame)
{
    format_t* format = &frame->list->format;
    IppiRawPixProcSpec_P3R* raw_spec;
    UINT y_size = format->width * format->height;

    PUCHAR dest[3];

    dest[0] = buf;
    dest[2] = dest[0] + y_size;
    dest[1] = dest[2]  + (y_size >> 2);

    NKDbgPrintfW(L"dest plane 0x%08x, 0x%08x, 0x%08x", dest[0], dest[1], dest[2]);

    // memset(pImage, 0, biSizeImage);
    raw_spec = format->width == 352? rpp_cif : rpp_uxga;

    rggb2yuv_convert(dest, frame, raw_spec);
    NKDbgPrintfW(L"done");
}

static void dump_Ov2630()
{
    unsigned char buf[0x8b];

    OV2630ReadAllRegs(buf, 0x8b);

    for (int i = 0; i < 0x8b; i++)
        NKDbgPrintfW(L"reg %x = %x", i, buf[i]);
}

#define IPP_COE_FLOAT_TO_INT(x)   ((short)((x) * (1 << 8) + 0.5))
static Ipp16s pMatrix[9];
static IppiRawPixProcCfg_P3R  ippiRPPCfg;
static IppiCAMCfg             ippiCAMCfg;

static IppiRawPixProcSpec_P3R *rggb2yuv_init(unsigned int width,
                                      unsigned int height,
                                      float* coe)
{
    IppiRawPixProcSpec_P3R *ippiRPPSpec = NULL;


    for (int i = 0; i < sizeof(pMatrix) / sizeof(pMatrix[0]); i++)
    {
        pMatrix[i] = IPP_COE_FLOAT_TO_INT(coe[i]);
        NKDbgPrintfW(L"matrix[%d] %d", i, pMatrix[i]);
    }
    /* Initializae CAMCfg structure */
    ippiCAMCfg.DPMLen		= 0;
    ippiCAMCfg.DPMOffset.x	= 0;
    ippiCAMCfg.DPMOffset.y	= 0;
    ippiCAMCfg.DPInterp		= ippCameraInterpNearest;
    ippiCAMCfg.GammaFlag	= ippGamPreOneTable;
    ippiCAMCfg.GammaIndex[0]	= 0;
    ippiCAMCfg.pCCMatrix	= pMatrix;
    ippiCAMCfg.pDeadPixMap	= NULL; /* if no dead pixel map */
    ippiCAMCfg.pGammaTable[0]	= NULL;
    ippiCAMCfg.pGammaTable[1]	= NULL;
    ippiCAMCfg.pGammaTable[2]	= NULL; 

    /* Initialize RPPCfg structure */
    ippiRPPCfg.interpolation	= ippCameraInterpBilinear;
    ippiRPPCfg.colorConversion	= ippCameraCscRGGBToYCbCr420;
    ippiRPPCfg.rotation		= ippCameraRotate180;;
    ippiRPPCfg.srcSize.width	= width;
    ippiRPPCfg.srcSize.height	= height;
    ippiRPPCfg.srcStep		= width << 1;

    ippiRPPCfg.dstSize.width	= width;
    ippiRPPCfg.dstSize.height	= height;
    ippiRPPCfg.dstStep[0]	= ippiRPPCfg.dstSize.width;
    ippiRPPCfg.dstStep[1]	= ippiRPPCfg.dstSize.width >> 1;
    ippiRPPCfg.dstStep[2]	= ippiRPPCfg.dstSize.width >> 1;
    
    ippiRPPCfg.bExtendBorder	= ippFalse;
    
    /* call init function */
    if (ippiInitAlloc_10RGGBtoYCbCr_RotRsz_P3R(&ippiCAMCfg, &ippiRPPCfg, &ippiRPPSpec) != ippStsOk)
    {
        NKDbgPrintfW(L"init ipp failed");
        return 0;
    };

    return ippiRPPSpec;
}

static BOOL rggb2yuv_convert(PUCHAR* dest, frame_t* frame, IppiRawPixProcSpec_P3R *ippiRPPSpec)
{
    format_t* format = &frame->list->format;
    Ipp8u *pSrc;
    //Ipp8u *pDst[3];
    int rv;

    pSrc = (Ipp8u*)frame->plane[0].buf.buf;
    NKDbgPrintfW(L"conver 0x%08x pSrc 0x%08x format 0x%08x", frame, pSrc, format);

    rv = ippi10RGGBtoYCbCr_RotRsz_8u_P3R(pSrc, dest, ippiRPPSpec);

    if (rv != ippStsOk)
            return FALSE;
    return TRUE;
}

static void rggb2yuv_deinit(IppiRawPixProcSpec_P3R *ippiRPPSpec)
{
    if (ippiRPPSpec)
        ippiFree_10RGGBtoYCbCr_RotRsz_P3R(ippiRPPSpec);
}

void Ov2630::write_sensor_reg(UCHAR addr, UCHAR data)
{
    OV2630WriteSensorReg(addr, &data);
}

void Ov2630::set_exposure_gain(USHORT time, USHORT gain)
{
    NKDbgPrintfW(L"exposure %d\n", time);

    UCHAR digi_gain1, digi_gain2;
    get_digi_gain(gain, &digi_gain1, &digi_gain2);

    UCHAR reg_aech = (UCHAR)((time & 0xfc00) >> 10);
    reg_aech |= digi_gain1 << 6;

    write_sensor_reg(0x8a, 0);
    write_sensor_reg(0x45, reg_aech);
    write_sensor_reg(0x4c, digi_gain2 << 5);
    write_sensor_reg(0x10, (UCHAR)((time & 0x3fc) >> 2));
    write_sensor_reg(0x04, (UCHAR)(time & 0x3));
    write_sensor_reg(0x0, get_analog_gain(gain));

}

UCHAR get_analog_gain(USHORT gain)
{
    unsigned int i = 0;

    if (gain > 32)
        gain = 32;

    UCHAR sensor_gain = 0;

    NKDbgPrintfW(L"gain %d\n", gain);

    while(gain > 2 && i < 4)
    {
        gain = gain / 2;
        sensor_gain |= 0x10 << i;
        i++;
    }


    UCHAR small_gain = (UCHAR)(gain * 16) - 16;

    if (small_gain >= 16)
        small_gain = 15;
    sensor_gain |= small_gain;
    
    NKDbgPrintfW(L"sensor gain 0x%x\n", sensor_gain);
    return sensor_gain;
}

UCHAR digi_gain_convert(UCHAR gain)
{
    if (gain <= 1)
        return 0;

    if (gain <= 3)
        return 1;
    else
        return 3;
}

void get_digi_gain(USHORT gain, UCHAR* digi_gain1, UCHAR* digi_gain2)
{
    UCHAR gain1 = 1, gain2 = 1;

    if (gain <= 32)
    {
        digi_gain1 = digi_gain2 = 0;
        return;
    }
		
    gain = gain / 32;

    if (gain > 4)
    {
        gain2 = gain / 4;
        gain1 = 4;
    } else
        gain1 = (UCHAR)gain;

    *digi_gain1 = digi_gain_convert(gain1);
    *digi_gain2 = digi_gain_convert(gain2);

}

